/*
 * Copyright (C) 2016 mzlion(and.mz.yq@gmail.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mzlion.easyokhttp.request;

import com.mzlion.core.lang.ArrayUtils;
import com.mzlion.core.lang.Assert;
import com.mzlion.core.lang.CollectionUtils;
import com.mzlion.core.lang.StringUtils;
import com.mzlion.easyokhttp.HttpClient;
import com.mzlion.easyokhttp.response.HttpResponse;
import com.mzlion.easyokhttp.response.callback.Callback;
import com.mzlion.easyokhttp.utils.CommonUtils;
import com.mzlion.easyokhttp.utils.CustomTrust;
import okhttp3.*;
import okio.Buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 2016-04-16 {@linkplain HttpRequest}的抽象实现，实现了大部分方法.
 * </p>
 *
 * @author mzlion
 */
@SuppressWarnings("unchecked")
public abstract class AbstractHttpRequest<Req extends HttpRequest<Req>> implements HttpRequest<Req> {
    private static String acceptLanguage;
    private static String userAgent;

    static {
        acceptLanguage = CommonUtils.getAcceptLanguage();
        userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36";
    }

    protected String url;

    /**
     * 连接超时时间
     */
    private int connectionTimeout;

    /**
     * 流读取超时时间
     */
    private int readTimeout;

    /**
     * 存储请求头信息
     */
    private Map<String, String> headers;

    private Map<String, String> queryParameters;

    /**
     * SSL证书文件列表
     */
    private InputStream[] certificates;

    AbstractHttpRequest(String url) {
        this.url = url;
        this.headers = new ConcurrentHashMap<>(10);
        this.queryParameters = new IdentityHashMap<>(10);

        //加载默认Header
        Map<String, String> defaultHeaders = HttpClient.INSTANCE.getDefaultHeaders();
        if (CollectionUtils.isNotEmpty(defaultHeaders)) {
            this.headers.putAll(defaultHeaders);
        }
    }

    /**
     * 设置请求地址
     *
     * @param url 请求地址
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req url(String url) {
        return (Req) this;
    }

    /**
     * 为url地址设置请求参数，即url?username=admin&nbsp;pwd=123
     *
     * @param name  参数名
     * @param value 参数值
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req queryString(String name, Number value) {
        Assert.hasLength(name, "Name must not be null.");
        Assert.notNull(value, "Value must not be null.");
        return this.queryString(name, value.toString());
    }

    /**
     * 为url地址设置请求参数，即url?username=admin&nbsp;pwd=123
     *
     * @param name  参数名
     * @param value 参数值
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req queryString(String name, String value) {
        if (StringUtils.hasLength(name) && StringUtils.hasText(value)) {
            queryParameters.put(name, value);
        }
        return (Req) this;
    }

    /**
     * 为url地址设置请求参数，即url?username=admin&nbsp;pwd=123
     *
     * @param parameters 参数对
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req queryString(Map<String, String> parameters) {
        if (CollectionUtils.isNotEmpty(parameters)) {
            for (String name : parameters.keySet()) {
                queryParameters.put(name, parameters.get(name));
            }
        }
        return (Req) this;
    }

    /**
     * 添加请求头信息
     *
     * @param key   请求头键名
     * @param value 请求头值
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req header(String key, String value) {
        if (StringUtils.hasLength(key) && StringUtils.hasLength(value)) {
            this.headers.put(key, value);
        }
        return (Req) this;
    }

    /**
     * 从请求头中移除键值
     *
     * @param key 请求头键名
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req removeHeader(String key) {
        if (StringUtils.hasLength(key)) {
            this.headers.remove(key);
        }
        return (Req) this;
    }

    /**
     * 为构建本次{@linkplain HttpRequest}设置单独连接超时时间。调用此方法会重新创建{@linkplain OkHttpClient}。
     *
     * @param connectionTimeout 连接超时时间
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req connectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
        return (Req) this;
    }

    /**
     * 为构建本次{@linkplain HttpRequest}设置单独读取流超时。
     *
     * @param readTimeout 流读取超时时间
     * @return 返回当前类{@linkplain Req}的对象自己
     */
    @Override
    public Req readTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
        return (Req) this;
    }

    /**
     * 为构建本次{@linkplain HttpRequest}设置单独SSL证书
     *
     * @param certificateContents SSL证书文件内容
     * @return 返回当前类{@link Req}的对象自己
     */
    public Req certificates(String... certificateContents) {
        Assert.notEmpty(certificateContents, "The certificate content must not be null or empty.");
        int length = certificateContents.length;
        InputStream[] certificateIns = new InputStream[length];
        for (int i = 0; i < length; i++) {
            certificateIns[i] = new Buffer().writeUtf8(certificateContents[i]).inputStream();
        }
        return this.certificates(certificateIns);
    }

    /**
     * 为构建本次{@linkplain HttpRequest}设置单独SSL证书
     *
     * @param certificates SSL证书文件
     * @return 返回当前类{@link Req}的对象自己
     */
    @Override
    public Req certificates(InputStream... certificates) {
        this.certificates = certificates;
        return (Req) this;
    }

    /**
     * 执行HTTP请求,获取响应结果
     *
     * @return 将响应结果转为具体的JavaBean
     */
    @Override
    public HttpResponse execute() {
        RequestBody requestBody = this.generateRequestBody();
        final Request request = this.generateRequest(requestBody);
        Call call = generateCall(request);
        try {
            Response response = call.execute();
            HttpResponse httpResponse = new HttpResponse();
            httpResponse.setRawResponse(response);
            httpResponse.setSuccess(response.isSuccessful());
            if (!response.isSuccessful()) {
                httpResponse.setErrorMessage(response.message());
                httpResponse.setSuccess(false);
                return httpResponse;
            } else {
                httpResponse.setSuccess(true);
                httpResponse.setErrorMessage(null);
            }
            return httpResponse;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void execute(Callback callback) {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取{@linkplain RequestBody}对象
     *
     * @return {@linkplain RequestBody}
     */
    protected abstract RequestBody generateRequestBody();

    /**
     * 根据不同的请求方式，将RequestBody转换成Request对象
     *
     * @param requestBody 请求体
     * @return {@link Request}
     * @see RequestBody
     */
    protected abstract Request generateRequest(RequestBody requestBody);

    /**
     * 执行请求调用
     *
     * @param request Request对象
     * @return {@linkplain Call}
     */
    private Call generateCall(final Request request) {
        if (this.readTimeout <= 0 && this.connectionTimeout <= 0 && ArrayUtils.isEmpty(this.certificates)) {
            return HttpClient.INSTANCE.getOkHttpClient().newCall(request);
        }
        OkHttpClient.Builder builder = HttpClient.INSTANCE.getOkHttpClient().newBuilder();
        if (this.connectionTimeout > 0) {
            builder.connectTimeout(this.connectionTimeout, TimeUnit.MILLISECONDS);
        }
        if (this.readTimeout > 0) {
            builder.readTimeout(this.readTimeout, TimeUnit.MILLISECONDS);
        }
        if (ArrayUtils.isNotEmpty(this.certificates)) {
            builder.sslSocketFactory(CustomTrust.sslSocketFactory(certificates));
        }
        return builder.build().newCall(request);
    }

    /**
     * 收集HTTP的请求头信息
     */
    Headers buildHeaders() {
        Headers.Builder builder = new Headers.Builder();
        //设置仿真浏览器信息
        this.simulator(builder);
        //设置默认请求头
        if (CollectionUtils.isNotEmpty(headers)) {
            for (String key : headers.keySet()) {
                builder.set(key, headers.get(key));
            }
        }
        return builder.build();
    }

    /**
     * 构建URL地址
     */
    String buildUrl() {
        Assert.hasLength(this.url, "Url must not be null.");
        StringBuilder sb = new StringBuilder(this.url);
        if (this.url.contains("?")) {
            sb.append("&");
        } else {
            sb.append("?");
        }
        Map<String, String> urlParameters = this.queryParameters;
        if (CollectionUtils.isNotEmpty(urlParameters)) {
            for (String name : urlParameters.keySet()) {
                try {
                    sb.append(name).append("=").append(URLEncoder.encode(urlParameters.get(name), StandardCharsets.UTF_8.name())).append("&");
                } catch (UnsupportedEncodingException e) {
                    //ignore
                }
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    private void simulator(Headers.Builder builder) {
        //添加 Accept-Language
        builder.add("Accept-Language", acceptLanguage);
        //添加 UserAgent
        builder.add("User-Agent", userAgent);
    }
}
